package com.esdk.sql;

import com.esdk.esdk;
import com.esdk.sql.orm.IRow;
import com.esdk.sql.orm.IRowSet;
import com.esdk.sql.orm.ParentRow;
import com.esdk.sql.orm.RowUtils;
import com.esdk.utils.EasySql;
import com.esdk.utils.EasyStr;
import com.esdk.utils.EasyTime;
import com.esdk.utils.TimeMeter;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;

/**批量insert into（注意oracle不支持该功能）*/
public class BatchInsert implements ISQL, IShowSql{
  protected String tableName;
  private Connection _conn;
  protected String insertSQL;
  protected String[] fields;
  protected List<Object[]> _values=new ArrayList<>();
  private boolean isAutoIncrement=true;
  private List generatedKeys;
	private boolean isShowSql=true;
	private int performCount=0;
	private boolean isCheckInjectSql=true;
	private SqlDialect sqlDialect=SQLAssistant.defaultDialect;
	private int retryTimes=0, retryIntervalMs=1000;
	private long executeConcumingTime;

	public BatchInsert(String tablename,Connection conn){
    this.tableName=tablename;
    setConnection(conn);
  }

  public BatchInsert(String tablename,String[] fields,Connection conn){
    this(tablename,conn);
    setFields(fields);
  }

	public void setCheckInjectSql(boolean v){
		isCheckInjectSql=v;
	}

  public String getSQL(){
  	parse();
    return insertSQL;
  }

	public String getTableName(){
		return tableName;
	}

  public void clear(){
    tableName=null;
    fields=null;
    _values.clear();
  }
  
  public void clearValues() {
  	_values.clear();
  }
  
  public int size() {
  	return _values.size();
  }
  
  public BatchInsert setFields(String... fields){
  	this.fields=fields;
  	return this;
  }
  
  public BatchInsert addValues(Object[] values){
		for(Object value:values)
			if(isCheckInjectSql)
				esdk.tool.throwOnTrue(SQLAssistant.checkInjectSql(esdk.str.valueOf(value)),"SQL注入风险："+value);
  	_values.add(values);
  	return this;
  }
  
  public BatchInsert addValues(Map values){
  	ArrayList list=new ArrayList(fields.length);
  	for(int i=0;i<fields.length;i++){
  		list.add(values.get(fields[i]));
  	}
  	return addValues(list.toArray());
  }
  
  public BatchInsert addValues(List<Object[]> list){
		for(Object[] item:list){
			addValues(item);
		}
		return this;
  }
  
  public <R extends IRow> BatchInsert addValues(Collection<R> collection){
		for(IRow row:collection) {
			addValues(row);
		}
		return this;
  }
	public <R extends IRow> BatchInsert addValues(IRowSet<R> rs){
		for(IRow row:rs) {
			addValues(row);
		}
		return this;
	}
  public BatchInsert addValues(ParentRow row) {
  	row.fillCommonFields();
		if(esdk.obj.isEmpty(fields)){
			if(row.isAutoIncrement())
				setFields(esdk.str.remove(row.getColumns(),row.getPrimaryKeyName()));
			else
				setFields(row.getColumns());
		}
		if(!row.isAutoIncrement() && row.getPKID() == null){
			row.setPKID(RowUtils.genNextPrimaryId());
		}
		_values.add(row.toList(fields).toArray());
  	return this;
  }
  
  public BatchInsert addValues(IRow row) {
  	if(row instanceof ParentRow parentRow){
			return this.addValues(parentRow);
		}
  	else{
			if(fields==null){
				fields=row.getColumns();
			}
			return addValues(row.toList().toArray());
		}
  }
  
  String getEllipseSql() {
  	StringBuffer result=new StringBuffer().append("Insert Into ").append(tableName).append(" (").append(EasyStr.arrToStr(fields)).append(")").append(" values ");
  	Object[] array=_values.get(0);
  	result.append("(");
  	for(int i=0,n=array.length;i<n;i++){
  		result.append(sqlDialect.getStmtSqlValue(array[i]));
  		if(i<n-1)
  			result.append(",");
  	}
  	result.append(")");
		result.append("...");
  	return result.toString(); 
  }
  
	public void parse(){
		StringBuilder sb=sqlDialect.parseBatchInsert(tableName,fields,_values);
		insertSQL=sb.toString();
	}
  /**如果达到或超出的数量则执行*/
  public int performIfOverload(int limit) throws SQLException{
  	if(size()>=limit) {
			int before=performCount;
  		int count=perform(true);
			performCount=before+count;
  		return count;
  	}
  	else
  		return 0;
  }

	/**自动分段执行*/
	public int perform(int limit,boolean autoCleanValues) throws SQLException{
		List<Object[]> all=_values;
		int count=size();
		for(int i=0,n=esdk.math.ceil(count,limit);i<n;i++){
			_values=all.subList(i,esdk.math.min((i+1)*limit,all.size()));
			perform(false);
		}
		if(autoCleanValues)
			clearValues();
		else
			_values=all;
		performCount=count;
		return performCount;
	}

  /**执行批处理sql并清空values值*/
  public int perform(boolean autoCleanValues) throws SQLException{
		performCount=_perform()?size():0;
  	if(performCount==size()&&autoCleanValues)
  		clearValues();
  	return performCount;
  }
	public int perform() throws SQLException{
		performCount=perform(true);
		return performCount;
	}

	@Override
	public long getExecuteConsuming(){
		return executeConcumingTime;
	}

	/**获取影响的记录数*/
	public int getPerformCount(){
		return performCount;
	}

  private boolean _perform() throws SQLException{
    if(_conn==null)
      throw new SQLRuntimeException("Connection is null,please invoke setConnectcion before perform");
    if(_values.size()==0)
    	return true;
		parse();
		try(Statement st=_conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY)){
			TimeMeter tm=TimeMeter.startNew();
			boolean flag=(SQLAssistant.isPostgreSQL()||SQLAssistant.isOracle())?st.execute(insertSQL):st.execute(insertSQL,Statement.RETURN_GENERATED_KEYS);
			executeConcumingTime=tm.getElapse();
			getGeneratedKeys(st);
			if(isShowSql)
			SQLAssistant.showSql(this);
		}catch(SQLException e){
			if(retryTimes>0 && e.getMessage().contains("Deadlock found")){
				esdk.error("可重试{}次批量插入SQL执行异常，错误提示：{}。执行的SQL：{}",retryTimes,e.toString(),this.getEllipseSql());
				retryTimes--;
				esdk.tool.sleep(this.retryIntervalMs);
				return _perform();
			}
			throw new SQLRuntimeException("批量插入SQL执行出现异常，错误提示："+e.toString()+"。执行的SQL为："+this.getEllipseSql(),e);
		}
		//return flag;; //待分析，目前测试在Mysql环境下，flag永远都是返回false.
		return true;
  }

	public BatchInsert retry(int retryTimes){
		this.retryTimes=retryTimes;
		return this;
	}

	public BatchInsert retry(int retryTimes,int retryIntervalMs){
		this.retryTimes=retryTimes;
		this.retryIntervalMs=retryIntervalMs;
		return this;
	}


	public List getGeneratedKeys() {
  	return generatedKeys;
  }
  
	private boolean getGeneratedKeys(Statement st) throws SQLException{
		if(isAutoIncrement()){
			generatedKeys=new ArrayList();
			if(SQLAssistant.isPostgreSQL()){
				ResultSet rs=st.getResultSet();
				while(rs!=null&&rs.next()) {
					generatedKeys.add(rs.getObject(1));
				}
			}else{
				ResultSet gkrs=st.getGeneratedKeys();
				while(gkrs!=null&&gkrs.next()){
					generatedKeys.add(gkrs.getObject(1));
				}
				EasySql.close(gkrs);
			}
		}
		return generatedKeys!=null;
	}

  public Connection getConnection(){
    return _conn;
  }

  public void setConnection(Connection conn){
    this._conn=conn;
		sqlDialect=SQLAssistant.setDefaultDialect(this._conn);
  }

	public void setAutoIncrement(boolean isAutoIncrement){
		this.isAutoIncrement = isAutoIncrement;
	}

	public boolean isAutoIncrement(){
		return isAutoIncrement;
	}

	@Override public BatchInsert showSql(boolean isShowSql){
		this.isShowSql=isShowSql;
		return this;
	}

	@Override public boolean showSql(){
		return this.isShowSql;
	}

	public static void test() {
    try{
    	BatchInsert bi=new BatchInsert("member",new String[]{"code","name","remark","sex","valid","create_time"},null);
    	bi.addValues(new Object[]{"CN001","测试名称1","who's your name?",2,true,EasyTime.valueOf("2010-01-02 18:01:59")});
    	bi.addValues(new Object[]{"CN002","测试名称2",null,2,true,EasyTime.valueOf("2010-03-02 23:01:01")});
    	esdk.tool.assertEquals(bi.getSQL(),"Insert Into member (code,name,remark,sex,valid,create_time) values ('CN001','测试名称1','who''s your name?',2,1,'2010-01-02 18:01:59'),('CN002','测试名称2',null,2,1,'2010-03-02 23:01:01')");
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }
  
  public static void main(String[] args){
    test();
  }

}
